﻿using System;
using System.IO;
using System.Net;
using System.Web;
using NLog;

namespace Softio.Mscd
{
    public class StaticFileHandler : IHttpAsyncHandler
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private const string Mime_CSS = "text/css";
        private const string Mime_JS = "application/x-javascript";
        private const string StaticFileHandlerTypeName = "Softio.Mscd.StaticFileHandler";

        static StaticFileHandler()
        {
            HandlerConfig.Configure();
        }

        public virtual IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object state)
        {
            bool useExpiredHeaders = false;
            HttpResponse response = context.Response;
            HttpRequest request = context.Request;
            response.Buffer = false;
            response.BufferOutput = false;
            try
            {
                HandlerHelper.EnsureProperRequest(request);
                Package package = HandlerHelper.TryGetPackage(context);

                DateTime? incomingModifiedSince = HandlerHelper.GetModifiedSinceHeaderValue(context);

                string physicalFilePath = request.PhysicalPath;
                
                int un = physicalFilePath.LastIndexOf("_v", StringComparison.OrdinalIgnoreCase);
                if (un > -1)
                {
                    int dot = physicalFilePath.LastIndexOf('.');
                    if (dot > -1)
                    {
                        physicalFilePath = physicalFilePath.Remove(un, dot - un);
                        useExpiredHeaders = true;
                    }
                }

                string cacheKey = String.Concat(StaticFileHandlerTypeName, physicalFilePath);

                if (package != null)
                {
                    package.GroupName = Path.GetFileNameWithoutExtension(physicalFilePath).TrimEnd('.');
                    physicalFilePath = Path.ChangeExtension(physicalFilePath, string.Empty);
                    CacheContent cc = HandlerHelper.GetPackageContent(context, package, cacheKey, physicalFilePath);

                    if (!HandlerHelper.ResponseModifiedSince(context.Response, incomingModifiedSince, cc.LastModifiedUtc, useExpiredHeaders))
                    {
                        if (Directory.Exists(physicalFilePath))
                        {
                            DirectoryInfo di = new DirectoryInfo(physicalFilePath);

                            cc.LastModifiedUtc = new DateTime(
                                di.LastWriteTimeUtc.Ticks - (di.LastWriteTimeUtc.Ticks % TimeSpan.TicksPerSecond),
                                di.LastWriteTimeUtc.Kind
                            );

                            HandlerHelper.WriteContent(
                                context,
                                cc,
                                HandlerHelper.GetCompressionMode(request),
                                package.Type == PackageType.Style ? Mime_CSS : Mime_JS,
                                useExpiredHeaders);
                        }
                        else
                        {
                            throw new HttpException((int)HttpStatusCode.NotFound, request.FilePath + " Not Found");
                        }
                    }
                    response.OutputStream.Flush();
                }
                else
                {
                    if (File.Exists(physicalFilePath))
                    {
                        FileInfo file = new FileInfo(physicalFilePath);
                        DateTime lastWriteTimeUtc = new DateTime(
                            file.LastWriteTimeUtc.Ticks - (file.LastWriteTimeUtc.Ticks % TimeSpan.TicksPerSecond),
                            file.LastWriteTimeUtc.Kind
                        );

                        if (!HandlerHelper.ResponseModifiedSince(context.Response, incomingModifiedSince, lastWriteTimeUtc, useExpiredHeaders))
                        {
                            string fileExtension = file.Extension.ToUpperInvariant();
                            if (HandlerHelper.IsKnownFile(fileExtension))
                            {

                                ResponseCompressionType compressionType = HandlerHelper.GetCompressionFromExtension(context.Request, fileExtension);
                                CacheContent cc = HandlerHelper.GetResourceContent(context, cacheKey, physicalFilePath, compressionType, lastWriteTimeUtc);
                                HandlerHelper.WriteContent(
                                    context,
                                    cc,
                                    compressionType,
                                    MimeMapping.GetMimeMapping(physicalFilePath),
                                    useExpiredHeaders);
                            }
                            else
                            {
                                HandlerHelper.ProduceResponseHeader(
                                    context.Response,
                                    Convert.ToInt32(file.Length),
                                    ResponseCompressionType.None,
                                    lastWriteTimeUtc,
                                    MimeMapping.GetMimeMapping(physicalFilePath),
                                    useExpiredHeaders);

                                response.TransmitFile(physicalFilePath);
                            }
                        }
                    }
                    else
                    {
                        throw new HttpException((int)HttpStatusCode.NotFound, request.FilePath + " Not Found");
                    }
                }
                return new HttpAsyncResult(cb, state, true, null, null);
            }
            catch (Exception x)
            {
                logger.Error("{0}\r\n{1}", x.Message, x.StackTrace);
                if (x is HttpException)
                {
                    throw;
                }
                return new HttpAsyncResult(cb, state, true, null, x);
            }
        }

        public virtual void EndProcessRequest(IAsyncResult result) { }
        public bool IsReusable { get { return true; } }
        public void ProcessRequest(HttpContext context) { throw new NotImplementedException(); }
    }
}
